from expression import expression, environment
from customExceptions import *

closureID = 0

class jsobject(expression):
	def __init__(self):
		self.slots = environment("",None)
	
	def access(self, name):
		return self.slots.getVarRef(name)
	
	def mutate(self, name, value):
		try:
			self.slots.mutateVarRef(name, value)
		except VarNotFoundExcpt:
			self.slots = environment(name, value, self.slots)
		return value
	
	def evaluate(self, env):
		return self
			
class closure(jsobject):
	def __init__(self, params, body, cloEnv):
		self.params = []
		self.params = params
		self.body = body
		self.cloEnv = cloEnv
	
	def evaluate(self, env):
		self.body.evaluate(env)
	
	def getEnv(self):
		return self.cloEnv
		
	def setEnv(self, env):
		self.cloEnv = env
	
	def getParams(self):
		return self.params

class stringval(jsobject):
	def __init__(self, string):
		self.string = string
		
	def evaluate(self, env):
		return self
		
	def getString(self):
		return self.string
		
	def __str__(self):
		return self.string
		
	def __add__(self, other):
		if(isinstance(other, stringval)):
			self.string = self.getString() + other.getString()
			return self
		else:
			raise GeneralException("Tried to add non-stringval to a stringval")
		
# applies closures
class new(expression):
	def __init__(self, funct, args):
		global closureID
		self.funct = funct
		self.args = args
		self.closureid = closureID
		closureID += 1
		
	#this may all be wrong		
	def evaluate(self, env):
		constructor =  self.funct.evaluate(env)
		params = constructor.getParams()
		
		
		if(isinstance(constructor, closure)): # function does return a closure
			o = jsobject()
			o.mutate("constructor", constructor)
			params = constructor.getParams()
			#append arguments, with correct bindings, to new enviroment
			#call function body

			if(len(self.args) != len(params)):
				raise GeneralException("New call with Function " + str(self.name) + " Has " + str(len(self.args)) + " args expected "+ str(len(params)))
			else:
				newEnv = constructor.getEnv()
				for arg, param in zip(self.args, params):
					# I think this line should do this:
					# for each parameter in the function, add a variable('x') to the envrionment
					# 'x' has the value of the associated argument, passed in the application
					# In addition, the new environment ('newEnv') is updated as params are added
					# but the old env ('env') is not updated - not that all the argument evaluations
					# use 'env'. I think this is the way to do it.
					newEnv = environment(param, arg.evaluate(env), newEnv)

				newEnv = environment("this", o, newEnv)
			
				try:
					constructor.evaluate(newEnv)
					return o
				except ReturnExcpt, ret:
					return o
		
	def __eq__(self, other):
		if(isinstance(other, closure)):
			return self.closureid == other.closureid
		else:
			return False
		
